#!/usr/bin/python3
# -*- coding: utf-8 -*
"""
Description: Base parser that have common function.
Class: Parser
"""
from pygrok import Grok

from ..libs.log import LOGGER


class Parser():
    """
    class "Parser" is the Base parser that parses the content.
    """

    def __init__(self, param_config, regex_config):
        """
        init.

        Args:
            regex_config (dict): configuration which keeps compiled patterns,
                                original patterns and other related information.
            param_config (dict): configuration which keeps corresponding log information.
        """
        self.param_config = param_config
        self.regex_config = regex_config

    def parse_content(self, key, content):
        """
        Main parse method that needs to be implemented by specified parser.

        Args:
            key (str): the main key of the content.
            content (str): raw data.
        """

    def parse_to_block(self, key, content):
        """
        Parse the content to the block by regex for the content may occupy multiple lines or
        have multiple formats.
        e.g.
        17/06/09 20:10:40 INFO executor.CoarseGrainedExecutorBackend:
        Registered signal handlers for [TERM, HUP, INT].

        Args:
            key (str): the main key of the content.
            content (list): the raw data.

        Returns:
            dict: each content after blocked.
        """
        log_type = self.param_config.get('log_type')
        # compile the multiline regex patterns.
        pattern_begin = self.param_config.get("multiline_begin_list")
        pattern_end = self.param_config.get("multiline_end_list")
        need_block = False
        # need block?
        if pattern_begin is not None and pattern_end is not None:
            grok_begin = self.param_config.get('grok_begin')
            grok_end = self.param_config.get('grok_end')
            if grok_begin is None:
                if isinstance(pattern_begin, list):
                    pattern_begin = '(' + '|'.join(pattern_begin) + ')'
                if isinstance(pattern_end, list):
                    pattern_end = '(' + '|'.join(pattern_end) + ')'
                try:
                    grok_begin = Grok(
                        pattern_begin, self.regex_config.get('custom_file'))
                    grok_end = Grok(
                        pattern_end, self.regex_config.get('custom_file'))
                    self.param_config['grok_begin'] = grok_begin
                    self.param_config['grok_end'] = grok_end
                    need_block = True
                except KeyError:
                    LOGGER.warning(
                        "Multiline grok compile failed for '%s', choose not blocked.", key)

        if need_block:
            is_head = self.param_config.get('head')
            # parse according to the log type
            if log_type == ['log'] and is_head is None:
                return self.parse_log_to_block(content, grok_begin, grok_end)
            if is_head is not None and len(is_head) != len(log_type):
                LOGGER.warning(
                    "Head and log type are not matched in '%s'.", key)
                is_head = None
            return self.parse_mix_to_block(content, grok_begin, grok_end, is_head)

        return content

    @staticmethod
    def parse_log_to_block(content, grok_begin, grok_end):
        """
        Parse the log content to the block considering that a piece of content may have multiple
        lines.

        Args:
            content (list): the raw data.
            grok_begin (grok object): the compiled pattern that used to match the begining of
                                      the block.
            grok_end (grok object): the compiled pattern that used to match the ending of the block.

        Returns:
            dict: each content after blocked.
        """

        if grok_begin is None:
            return content

        res = []
        i = 0

        while i < len(content):
            cur_begin = grok_begin.match(content[i])
            if cur_begin is not None:
                j = i + 1
                while j < len(content):
                    cur_end = grok_end.match(content[j])
                    if cur_end is not None:
                        res.append(''.join([line.strip()
                                            for line in content[i: j]]))
                        i = j
                        break
                    j += 1
                if j >= len(content):
                    res.append(''.join([line.strip()
                                        for line in content[i: j]]))
                    break
            else:
                i += 1

        return res

    def parse_mix_to_block(self, content, grok_begin, grok_end, is_head):
        """
        Parse the log content to the block considering that there are
        different kinds of log types or additional information in the
        content, e.g. kbox_log.

        Args:
            content (list): the raw data.
            grok_begin (grok object): the compiled pattern that used to match the begining of
                                      the block.
            grok_end (grok object): the compiled pattern that used to match the ending of the
                                    block.
            is_head (list): head of each blocked content.

        Returns:
            dict: each content after blocked.
        """

        res = dict()
        i = 0
        count = 0

        while i < len(content):
            cur_begin = grok_begin.match(content[i])
            if cur_begin is None:
                i += 1
                continue
            j = i + 1
            tmp_content = []
            tmp_content.append(content[i])
            while j < len(content):
                # invalid lines
                if content[j].startswith('---') or content[j].startswith('===')\
                        or content[j].startswith('***'):
                    j += 1
                    continue
                cur_end = grok_end.match(content[j])
                if cur_end is not None:
                    subkey, data = self.get_content(
                        is_head, tmp_content, count)
                    res[subkey] = data
                    count += 1
                    i = j
                    if is_head is not None and count == len(is_head):
                        return res
                    break
                tmp_content.append(content[j])
                j += 1
            if j >= len(content):
                subkey, data = self.get_content(
                    is_head, tmp_content, count)
                res[subkey] = data
                break

        return res

    @staticmethod
    def get_content(is_head, content, count):
        """
        A function to get the key and content of the block.

        Args:
            is_head (list): head of each blocked content.
            content (list): the blocked content.
            count (int): corresponding head index.

        Returns:
            str: key
            list: content
        """
        if is_head is not None and is_head[count] != "":
            subkey = is_head[count]
            data = content
        else:
            subkey = content[0].strip(' :*,-\n[]#%')
            data = content[1:]
        return subkey, data

    @staticmethod
    def get_active_lines(lines, comment_char="#"):
        """
        Returns lines, or parts of lines, from content that are not commented out
        or completely empty. The resulting lines are all individually stripped.

        Args:
            lines (list): list of strings to parse.
            comment_char (str): string indicating that all chars following
                are part of a comment and will be removed from the output.

        Returns:
            list: list of valid lines remaining in the input.
        """

        if comment_char == "":
            return list(filter(None, (line.strip() for line in lines)))

        return list(filter(None, (line.split(comment_char, 1)[0].strip() for line in lines)))

    def get_matched_pattern(self, key, head, index=0):
        """
        Try regex pattern from the param configuration, or turn to find matched pattern in
        regex configuration.

        Args:
            key (str): key of the content.
            head (str): head of the content, just used to find the matched pattern.
            index (int): determine which regex pattern customed should be used.

        Returns:
            grok object: matched compiled pattern.
            str: matched pattern.
        """
        matched_grok = None
        matched_pattern = None

        if "regex_pattern" in self.param_config.keys():
            regex_pattern = self.param_config.get('regex_pattern')
            regex_pattern = regex_pattern if isinstance(
                regex_pattern, list) else [regex_pattern]
            pattern = regex_pattern[index]
            try:
                grok = Grok(pattern, self.regex_config.get('custom_file'))
                tmp = grok.match(head)
                if tmp is not None and tmp != {}:
                    matched_grok = grok
                    matched_pattern = pattern
            except KeyError:
                LOGGER.error(
                    "Try to use custom pattern '%s' of '%s', but it compiled failed.", pattern, key)

        return matched_grok, matched_pattern

    def find_matched_pattern_by_type(self, head, file_type, second=None):
        """
        Try to find matched pattern in regex configuration by type.

        Args:
            head (str): head of the content, just used to find the matched pattern.
            file_type (str): log type.
            second (str): second line of the content, used to check the matched pattern.

        Returns:
            grok object: matched compiled pattern.
            str: matched pattern.
        """
        grok = self.regex_config.get('grok')

        if grok is None or file_type not in grok.keys():
            return None, None

        for matched_index, matched_grok in enumerate(grok.get(file_type)):
            tmp = matched_grok.match(head)
            if tmp is not None and tmp != {}:
                if second is not None and matched_grok.match(second) is None:
                    continue
                return matched_grok, self.regex_config['patterns'][file_type][matched_index]

        return None, None
